package com.yum.ms.controller;

import com.alibaba.fastjson.JSON;
import com.yum.ms.entity.*;
import com.yum.ms.entity.vo.GoodsVO;
import com.yum.ms.mq.MQSender;
import com.yum.ms.service.IGoodsService;
import com.yum.ms.service.IOrderService;
import com.yum.ms.service.ISeckillOrderService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @author Yum
 * @version 1.0
 */
@RestController
@RequestMapping("/seckill")
@Slf4j
public class SeckillController  implements InitializingBean{

    @Autowired
    private IGoodsService goodsService;
    @Autowired
    private ISeckillOrderService seckillOrderService;
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private MQSender mqSender;
    @Autowired
    private IOrderService orderService;

    /*
        用于内存判断库存是否为空
        Long :商品id
     */
    private Map<Long,Boolean> EmptyStockMap = new HashMap<>();

    /**
     * 获取秒杀结果
     * @param userId
     * @param goodsId
     * @return
     */
    @GetMapping("/result")
    public String getResult(Long userId, Long goodsId) {
        if(userId == null) {
            return "用户不存在，请先登录！";
        }
        String result = seckillOrderService.getResult(userId,goodsId);
        return result;
    }

    /**
     * 获取秒杀地址
     * @param userId
     * @param goodsId
     * @return
     */
    @GetMapping("/path")
    public String getPath(@RequestParam Long userId,
                          @RequestParam Long goodsId, HttpServletRequest request) {
        if(userId == null) {
            return "未登录或登录已过期，请先登录！";
        }
        // 接口限流
        StringBuffer url = request.getRequestURL();
        ValueOperations valueOperations = redisTemplate.opsForValue();
        Integer count = (Integer) valueOperations.get(url+":"+userId);
        if(count == null) {
            // 当redis中没有计数时开始计数(5秒)
            valueOperations.set(url+":"+userId,1,5, TimeUnit.SECONDS);
        }else if(count < 5) {
            // 5秒内访问次数最多5次
            valueOperations.increment(url + ":" + userId);
        }else {
            // 超过5次
            return "请求次数过多，请稍后再试！";
        }
        return orderService.createPath(userId,goodsId);
    }

    /**
     * 秒杀
     * @param userId
     * @param goodsId
     * @return
     */
    @RequestMapping("/doSeckill")
    public String doSeckill(@RequestParam String path,
                            @RequestParam Long userId,
                            @RequestParam Long goodsId) {
        if(userId == null) {
            return "登录或登录已过期，请先登录！";
        }
        User user = new User();
        user.setId(userId);
        // 确认秒杀地址
        boolean check = orderService.checkPath(path,user,goodsId);
        if(!check) {
            // 秒杀地址不正确
            return "非法请求！";
        }

        ValueOperations valueOperations = redisTemplate.opsForValue();
        // redis操作
        // 判断是否重复抢购
        SeckillOrder seckillOrder = (SeckillOrder)
                valueOperations.get("order:" + user.getId() + ":" + goodsId);
        if(seckillOrder != null) {
            return "秒杀失败，重复抢购！";
        }
        // 通过内存判断是否空库存(因为一旦预减库存为空，后面的请求还是会发送到redis)
        // 内存标记,这样做可以减少redis的压力
        if(EmptyStockMap.get(goodsId)) {
            return "空库存";
        }
        // redis预减库存
        Long stock = valueOperations.decrement("seckillGoods:" + goodsId);
        if(stock < 0) {
            EmptyStockMap.put(goodsId,true);
            // 当库存为0时redis还是会递减变成-1,才会返回,所以我们这里递加(仅仅改变了redis显示,没有影响)
            valueOperations.increment("seckillGoods:" + goodsId);
            return "库存不足!";
        }
        // 下单
        SeckillMessage seckillMessage = new SeckillMessage(user, goodsId);
        // mq异步处理
        mqSender.sendSeckillMessage(JSON.toJSONString(seckillMessage));
        // 为了快速响应返回排队中
        return "排队中";
    }

    /**
     * 系统初始化,把商品库存数量加载到redis里面
     * @throws Exception
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        List<GoodsVO> list = goodsService.selectGoodsVOList();
        if(list.isEmpty()) {// 判断商品是否为空
            // 为空直接返回
            return;
        }
        // 不为空，将商品数据存到redis中
        // 对于forEachOrdeed ，当stream 为parallel的时候，尽管是多个线程，并行处理的。
        // 但是还是会按照他source原有的顺序输出
        // 底层是通过happensbefore原则保证了它的内存可见性。（可以理解为相当于用volatile修饰了吧）
        list.parallelStream().forEachOrdered(goodsVO -> {
            redisTemplate.opsForValue().set("seckillGoods:" + goodsVO.getId(), goodsVO.getStockCount());
            // 初始化有库存
            EmptyStockMap.put(goodsVO.getId(),false);
        });
    }
}
